package org.inspiration.dudu.common.util;

import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
@RequiredArgsConstructor
public class RedisUtil {


    private final RedisTemplate<String, Object> redisTemplate;
    // 分布式锁
    private static final Long RELEASE_SUCCESS = 1L;
    private static final String RELEASE_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else " +
            "return 0 " +
            "end";

    private static final String LOCK_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 10; // 锁的过期时间，单位：秒
    private static final long RETRY_INTERVAL = 100; // 重试间隔，单位：毫秒

    /**
     * 获取以指定前缀开头的所有键
     *
     * @param keyPrefix 键前缀
     * @return 匹配的键集合
     */
    public Set<String> getKeysByPrefix(String keyPrefix) {
        return redisTemplate.keys(keyPrefix + "*");
    }

    /**
     * 查询缓存，如果缓存不存在则从数据库获取并更新缓存
     *
     * @param keyPrefix      缓存键前缀
     * @param id             唯一标识，支持任意类型
     * @param resultType     结果类型
     * @param queryFunction  查询数据库的函数，接收 id 作为参数
     * @param expirationTime 过期时间
     * @param timeUnit       时间单位
     * @param <ID>           id 的类型
     * @param <T>            结果类型
     * @return 查询结果
     */

    public <ID, T> T queryStringCache(String keyPrefix, ID id, Class<T> resultType, Function<ID, T> queryFunction, long expirationTime, TimeUnit timeUnit) {
        String key = keyPrefix + id.toString();
        String lockKey = LOCK_PREFIX + key;

        // 从缓存中获取数据
        String cachedValue = getString(key);
        if (cachedValue != null) {
            if (cachedValue.isEmpty()) {
                // 处理缓存穿透，返回 null 表示数据不存在
                return null;
            }
            return JSONUtil.toBean(cachedValue, resultType);
        }

        boolean isLocked = false;
        try {
            // 尝试获取锁
            while (!isLocked) {
                isLocked = lock(lockKey);
                if (!isLocked) {
                    // 未获取到锁，等待一段时间后重试
                    Thread.sleep(RETRY_INTERVAL);
                }
            }

            // 再次检查缓存，因为可能在等待锁的过程中缓存已被更新
            cachedValue = getString(key);
            if (cachedValue != null) {
                if (cachedValue.isEmpty()) {
                    return null;
                }
                return JSONUtil.toBean(cachedValue, resultType);
            }

            // 从数据库查询数据
            T result = queryFunction.apply(id);
            // 处理缓存穿透，缓存空值
            // 将数据存入缓存，并设置随机过期时间以处理缓存雪崩
            setString(key, Objects.requireNonNullElse(JSONUtil.toJsonStr(result), ""), getRandomExpiration(expirationTime), timeUnit);
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (isLocked) {
                unlock(lockKey);
            }
        }
    }

    public <ID, T> T queryHashCache(String keyPrefix, ID id, Class<T> resultType, Function<ID, T> queryFunction, long expirationTime, TimeUnit timeUnit) {
        String key = keyPrefix + id.toString();
        String lockKey = LOCK_PREFIX + key;

        // 从缓存中获取数据
        Map<Object, Object> cachedValue = getAllHash(key);
        if (!cachedValue.isEmpty()) {
            if ("false".equals(cachedValue.get("exists"))) {
                // 处理缓存穿透，返回 null 表示数据不存在
                return null;
            }
            return mapToObject(cachedValue, resultType);
        }

        boolean isLocked = false;
        try {
            // 尝试获取锁
            while (!isLocked) {
                isLocked = lock(lockKey);
                if (!isLocked) {
                    // 未获取到锁，等待一段时间后重试
                    Thread.sleep(RETRY_INTERVAL);
                }
            }

            // 再次检查缓存，因为可能在等待锁的过程中缓存已被更新
            cachedValue = getAllHash(key);
            if (!cachedValue.isEmpty()) {
                if ("false".equals(cachedValue.get("exists"))) {
                    return null;
                }
                return mapToObject(cachedValue, resultType);
            }

            // 从数据库查询数据
            T result = queryFunction.apply(id);
            // 处理缓存穿透，缓存空值
            // 将数据存入缓存，并设置随机过期时间以处理缓存雪崩
            setObjectAsHash(key, result, getRandomExpiration(expirationTime), timeUnit);
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (isLocked) {
                unlock(lockKey);
            }
        }
    }

    /**
     * 获取锁
     *
     * @param lockKey 锁的键名
     * @return 是否成功获取锁
     */
    private boolean lock(String lockKey) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", LOCK_EXPIRE_TIME, TimeUnit.SECONDS));
    }

    /**
     * 释放锁
     *
     * @param lockKey 锁的键名
     */
    private void unlock(String lockKey) {
        redisTemplate.delete(lockKey);
    }

    /**
     * 获取随机过期时间，处理缓存雪崩
     *
     * @param baseExpiration 基础过期时间
     * @return 随机过期时间
     */
    private long getRandomExpiration(long baseExpiration) {
        // 生成一个小的随机偏移量，例如 0 - 60 秒
        long offset = (long) (Math.random() * 60);
        return baseExpiration + offset;
    }


// 字符串操作 --------------------------------------------------------

    // 设置字符串值
    public void setString(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 获取字符串值
    public String getString(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    // 设置字符串值，指定过期时间
    public void setString(String key, String value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 自增
    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    // 获取字符串长度
    public Long getStringLength(String key) {
        return redisTemplate.opsForValue().size(key);
    }

// 哈希操作 --------------------------------------------------------

    /**
     * 将对象存储为 Redis 的哈希数据结构
     *
     * @param key Redis 哈希的键
     * @param obj 要存储的对象
     */
    public void setObjectAsHash(String key, Object obj, long timeout, TimeUnit unit) {
        Map<String, Object> hashMap = new HashMap<>();
        if (obj == null) {
            hashMap.put("exists", "false");
        } else {
            hashMap.put("exists", "true");
            Class<?> clazz = obj.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    Object value = field.get(obj);
                    if (value != null) {
                        hashMap.put(field.getName(), value);
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        }
        if (!hashMap.isEmpty()) {
            redisTemplate.opsForHash().putAll(key, hashMap);
            redisTemplate.expire(key, timeout, unit);
        }
    }

    // 设置哈希字段值
    public void setHash(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 对 Redis 哈希中指定字段的值加一
     *
     * @param key     Redis 哈希的键
     * @param hashKey 哈希字段名
     * @return 加一后的值
     */
    public Long incrementHashFieldWithLua(String key, String hashKey) {
        String script = "if redis.call('HEXISTS', KEYS[1], KEYS[2]) == 1 then return redis.call('HINCRBY', KEYS[1], KEYS[2], 1) else return nil end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        List<String> keys = Arrays.asList(key, hashKey);
        return redisTemplate.execute(redisScript, keys);
    }

    public Long decrementHashFieldWithLua(String key, String hashKey) {
        String script = "if redis.call('HEXISTS', KEYS[1], KEYS[2]) == 1 then return redis.call('HINCRBY', KEYS[1], KEYS[2], -1) else return nil end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        List<String> keys = Arrays.asList(key, hashKey);
        return redisTemplate.execute(redisScript, keys);
    }

    // 获取哈希字段值
    public Object getHash(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    // 获取哈希中所有字段和值
    public Map<Object, Object> getAllHash(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 将 Redis 哈希数据赋值给对象
     *
     * @param map         包含哈希数据的 Map
     * @param targetClass 目标对象的类
     * @param <T>         目标对象的类型
     * @return 赋值后的对象
     */
    public <T> T mapToObject(Map<Object, Object> map, Class<T> targetClass) {
        try {
            T obj = targetClass.getDeclaredConstructor().newInstance();
            Field[] fields = targetClass.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String fieldName = field.getName();
                if (map.containsKey(fieldName)) {
                    Object value = map.get(fieldName);
                    if (field.getType() == Long.class && value instanceof Integer) {
                        value = ((Integer) value).longValue();
                    } else if (field.getType() == LocalDateTime.class) {
                        if (value instanceof String) {
                            // 假设日期时间字符串格式为 "yyyy-MM-dd HH:mm:ss"
                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                            value = LocalDateTime.parse((String) value, formatter);
                        }
                    }
                    field.set(obj, value);
                }
            }
            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public <T> T getAllHashAsObject(String key, Class<T> targetClass) {
        return mapToObject(getAllHash(key), targetClass);
    }

    // 判断哈希字段是否存在
    public Boolean hasHashField(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    // 删除哈希中的字段
    public void deleteHashField(String key, String hashKey) {
        redisTemplate.opsForHash().delete(key, hashKey);
    }

// 集合操作 --------------------------------------------------------

    // 向集合添加元素
    public void addSet(String key, Object... values) {
        redisTemplate.opsForSet().add(key, values);
    }

    // 获取集合中的所有元素
    public Set<Object> getSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    // 判断元素是否在集合中
    public Boolean isMemberOfSet(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    // 移除集合中的元素
    public void removeSet(String key, Object... values) {
        redisTemplate.opsForSet().remove(key, values);
    }

// 有序集合操作 --------------------------------------------------------

    // 向有序集合添加元素
    public void addZSet(String key, Object value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    // 获取有序集合的所有元素（按分数排序）
    public Set<Object> getZSet(String key) {
        return redisTemplate.opsForZSet().range(key, 0, -1);
    }

    // 分页查询有序集合的元素
    public Set<Object> getZSetByPage(String key, int pageNum, int pageSize) {
        long start = (long) (pageNum - 1) * pageSize;
        long end = start + pageSize - 1;
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    // 获取有序集合中指定分数范围的元素
    public Set<Object> getZSetByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    // 获取有序集合中指定元素的分数
    public Double getZSetScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    // 移除有序集合中的元素
    public void removeZSet(String key, Object value) {
        redisTemplate.opsForZSet().remove(key, value);
    }

    // 判断有序集合中是否存在某个 member
    public boolean isZSetMemberExists(String key, Object value) {
        Double score = redisTemplate.opsForZSet().score(key, value);
        return score != null;
    }

    // 获取有序集合的元素数量
    public Long getZSetSize(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }
// 列表操作 --------------------------------------------------------

    // 向列表尾部添加元素
    public void addToList(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    // 向列表头部添加元素
    public void addToListLeft(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    // 获取列表中的所有元素
    public List<Object> getList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    // 获取列表长度
    public Long getListLength(String key) {
        return redisTemplate.opsForList().size(key);
    }

    // 从列表头部移除元素
    public Object removeFromList(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

// 通用操作 --------------------------------------------------------

    // 判断键是否存在
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    // 删除键
    public Boolean deleteKey(String key) {
        return redisTemplate.delete(key);
    }

    // 获取键的过期时间
    public Long getKeyExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    // 设置键的过期时间
    public Boolean setKeyExpire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

// 分布式锁操作 --------------------------------------------------------

    // 获取分布式锁
    public Boolean acquireLock(String lockKey, String requestId, long timeout, TimeUnit unit) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, timeout, unit);
    }

    // 释放分布式锁
    public Boolean releaseLock(String lockKey, String requestId) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(RELEASE_SCRIPT);
        redisScript.setResultType(Long.class);

        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        return RELEASE_SUCCESS.equals(result);
    }

    // 自定义脚本操作
    public <T> T executeScript(String script, List<String> keys, List<String> args, Class<T> resultType) {
        DefaultRedisScript<T> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(resultType);
        return redisTemplate.execute(redisScript, keys, args);
    }
}